package jmine.tec.batch;

import java.util.concurrent.Callable;

import jmine.tec.batch.context.BatchExecutionContext;
import jmine.tec.batch.event.EventQueue;
import jmine.tec.batch.event.domain.Event;
import jmine.tec.batch.event.domain.enumx.EventLevelEnum;
import jmine.tec.batch.event.domain.enumx.EventStatusEnum;
import bancosys.tec.persist.controller.AbstractPersistenceEnabledController;
import bancosys.tec.persist.controller.TransactionalController;
import bancosys.tec.rtm.impl.RtmController;

/**
 * @author jrenaut
 */
public class BatchController extends AbstractPersistenceEnabledController {

    private static final ThreadLocal<BatchExecutionContext> CONTEXT_THREAD_LOCAL = new ThreadLocal<BatchExecutionContext>();

    private TransactionalController transactionalController;

    private ExecPhaseFactory execPhaseFactory;

    private RtmController rtmController;

    private final EventQueue eventQueue = new EventQueue(this);

    private boolean eventCacheActive = true;

    private EventLevelEnum eventCutLevel = null;

    /**
     * Executes the given callable in a transactional context.
     * 
     * @param <T> the return type of the Callable
     * @param callable a callable
     * @return the result of calling this callable
     * @throws Exception as per the Callable interface
     */
    public <T> T executeTransacted(Callable<T> callable) throws Exception {
        return this.transactionalController.executeTransacted(callable);
    }

    /**
     * Devolve o kernel context.
     * 
     * @return o kernel context.
     */
    public BatchExecutionContext getBatchExecutionContext() {
        BatchExecutionContext context = CONTEXT_THREAD_LOCAL.get();
        if (context == null) {
            context = new BatchExecutionContext();
            CONTEXT_THREAD_LOCAL.set(context);
        }
        return context;
    }

    /**
     * Limpa o kernel context.
     */
    public void clearBatchExecutionContext() {
        this.getBatchExecutionContext().clear();
    }

    /**
     * Inicializa o cache de eventos do controller.
     */
    public void init() {
        this.eventQueue.start();
    }

    /**
     * Devolve <code>true</code> se o tratamento de eventos estiver ligado, <code>false</code> caso contrário.
     * 
     * @return <code>true</code> se o tratamento de eventos estiver ligado, <code>false</code> caso contrário.
     */
    public boolean isEventCacheActive() {
        return this.eventCacheActive;
    }

    /**
     * Seta o eventCache flag.
     * 
     * @param active set o eventCache flag.
     */
    public void setEventCacheActive(boolean active) {
        this.eventCacheActive = active;
    }

    /**
     * Ativa o tratamento de eventos.
     */
    public void enableEvent() {
        this.eventCacheActive = true;
        this.eventQueue.start();
    }

    /**
     * Desativa o tratamento de eventos, esperando para que os eventos previamente adicionados sejam processados.
     * 
     * @throws InterruptedException se a thread atual for interrompida enquanto os eventos nao terminaram de processar
     */
    public void disableEvent() throws InterruptedException {
        this.eventCacheActive = false;
        this.eventQueue.requestStop();
    }

    /**
     * Devolve o nível máximo de evento que será registrado.
     * 
     * @return o nível máximo de evento que será registrado.
     */
    public EventLevelEnum getEventCutLevel() {
        return this.eventCutLevel;
    }

    /**
     * Define o nível máximo de evento que será registrado.
     * 
     * @param eventCutLevel o nível máximo de evento que será registrado.
     */
    public void setEventCutLevel(EventLevelEnum eventCutLevel) {
        this.eventCutLevel = eventCutLevel;
    }

    /**
     * Lanca um evento.
     * 
     * @param event evento.
     */
    public void throwEvent(Event event) {
        if (this.cutEvent(event)) {
            return;
        }

        event.acquireContextData(this.getBatchExecutionContext());

        switch (event.getActionMode()) {
        case SEND:
            event.execute(this);
            break;
        case DISPATCH:
            event.setStatus(EventStatusEnum.WAITING);
            if (this.isEventCacheActive()) {
                this.eventQueue.throwEvent(event);
            }
            break;
        default:
            throw new BatchException(JmineTecBatchMessages.EVENT_UNKOWN_ACTION_MODE.create(event.getActionMode()));
        }
    }

    /**
     * Decide se um evento deve ou nao ser registrado em função do seu "nível".
     * 
     * @param event evento
     * @return <code>true</code> se deve ser cortado, <code>false</code> caso deva ser registrado.
     * @see #cutEventLevel(EventLevelEnum)
     */
    public final boolean cutEvent(Event event) {
        return this.cutEventLevel(event.getLevel());
    }

    /**
     * Decide se o nivel passado deve ser cortado. O {@link EventLevelEnum} sera cortado se for maior do que o campo {@link #eventCutLevel}
     * ou se este campo for <code>null</code>
     * 
     * @param level evento
     * @return <code>true</code> se deve ser cortado, <code>false</code> caso deva ser registrado.
     */
    public final boolean cutEventLevel(EventLevelEnum level) {
        if (this.eventCutLevel == null) {
            return false;
        }
        return level.compareTo(this.eventCutLevel) < 0;
    }

    /**
     * @return the transactionalController
     */
    public TransactionalController getTransactionalController() {
        return this.transactionalController;
    }

    /**
     * @param transactionalController the transactionalController to set
     */
    public void setTransactionalController(TransactionalController transactionalController) {
        this.transactionalController = transactionalController;
    }

    /**
     * @return the execPhaseFactory
     */
    public ExecPhaseFactory getExecPhaseFactory() {
        return this.execPhaseFactory;
    }

    /**
     * @return the rtmController
     */
    public RtmController getRtmController() {
        return this.rtmController;
    }

    /**
     * @param execPhaseFactory the execPhaseFactory to set
     */
    public void setExecPhaseFactory(ExecPhaseFactory execPhaseFactory) {
        this.execPhaseFactory = execPhaseFactory;
    }

    /**
     * @param rtmController the rtmController to set
     */
    public void setRtmController(RtmController rtmController) {
        this.rtmController = rtmController;
    }

    /**
     * @return the eventQueue
     */
    public EventQueue getEventQueue() {
        return this.eventQueue;
    }

}
